import copy
from functools import reduce
from common.logger import log
import workload.registered_workloads as registered_workloads
from workload.workloads_manager import WorkloadManager
from common.benchmark_var import ConfigSections as Sections, ResultType, ConfigOptions as Options


class Suite:
    def __init__(self, name: str, subsuites: list, namespace=None, options=None):
        self.name = name
        self.type = "Base"
        self.sub_suites = subsuites
        self.workloads = []
        self.baseline = None
        self.description = ""
        self.score = 0
        self.valid = True
        self.options = options
        if not namespace:
            self.namespace = self.name
        else:
            self.namespace = "{}.{}".format(namespace, self.name)
        self.wd_multi_count = 0
        self.wd_single_count = 0

    @property
    def support_multi_mode(self):
        if self.sub_suites:
            if any(suite.support_multi_mode is True for suite in self.sub_suites):
                return True
        return True if self.wd_multi_count > 0 else False

    @property
    def support_single_mode(self):
        if self.sub_suites:
            if any(suite.support_single_mode is True for suite in self.sub_suites):
                return True
        return True if self.wd_single_count > 0 else False


    def build(self):
        raise NotImplementedError

    def run(self):
        raise NotImplementedError

    def validate(self):
        raise NotImplementedError

    def calc_score(self):
        raise NotImplementedError

    def clean(self):
        # clean workloads
        for workload in self.workloads:
            workload.clean()
        # clean subsuites
        for suite in self.sub_suites:
            suite.clean()

    def create_workloads(self, workload_list: list):
        workload_objs = []
        if not workload_list:
            return workload_objs

        copies = self.options[Sections.GENERAL][Options.COPIES]
        for workload_name in workload_list:
            WorkloadClass = WorkloadManager.get_workload_class(workload_name)        
            workload_options = self.get_option_for_workload(workload_name)
            workload_baseline = self.get_baseline_for_workload(workload_name)
            workload_obj = WorkloadClass(workload_name, workload_options, workload_baseline)

            if copies > 1 and workload_obj.support_multi_mode:
                workload_objs.append(workload_obj)
            elif copies == 1 and workload_obj.support_single_mode:
                workload_objs.append(workload_obj)

            if workload_obj.support_multi_mode:
                self.wd_multi_count += 1
            if workload_obj.support_single_mode:
                self.wd_single_count += 1

        self.workloads = workload_objs

    def merge_custom_option(self, custom_options: dict, other_options: dict):
        for key in other_options:
            if key not in custom_options:
                custom_options[key] = other_options[key]
        return custom_options

    def get_parent_namespace(self, namespace):
        if not namespace:
            return None
        name_list = namespace.split(".")
        if "_" in name_list[-1]:
            name_list = name_list[:-1] + name_list[-1].split("_")[:2]
        if len(name_list) < 2:
            return None
        return ".".join(name_list[:-1])

    def get_baseline_for_workload(self, workload_name):
        if self.baseline:
            try:
                workload_baseline = {workload_name: self.baseline[workload_name]}
            except KeyError:
                workload_baseline = {workload_name: 1}
                log.error("No baseline for workload: {}".format(workload_name))

            for item, baseline in self.baseline.items():
                if "{}.".format(workload_name) in item:
                    workload_baseline[item] = baseline
        else:
            workload_baseline = {workload_name: 1}
            log.error("No baseline for workload: {}".format(workload_name))

        return workload_baseline

    def get_option_for_workload(self, workload_name):
        general_options = self.options[Sections.GENERAL]
        build_options = copy.deepcopy(self.options[Sections.COMPILER])

        cur_namespace = self.namespace + "." + workload_name
        custom_options = dict()
        while cur_namespace:
            if cur_namespace in self.options:
                custom_options = self.merge_custom_option(custom_options, self.options[cur_namespace])
            cur_namespace = self.get_parent_namespace(cur_namespace)
        build_options = self.merge_custom_option(custom_options, build_options)
        options = OptionDict()
        options[Sections.GENERAL] = general_options
        options[Sections.COMPILER] = build_options
        options.set_attributes()
        return options

    def get_result(self):
        res = dict()
        if self.workloads:
            workload_score_list = list()
            for workload in self.workloads:
                workload_score_list.append(workload.get_result())
                res["workloads"] = workload_score_list
        else:
            sub_suite_score_list = list()
            for suite in self.sub_suites:
                sub_suite_score_list.append(suite.get_result())
                res["sub_suites"] = sub_suite_score_list
        res["name"] = self.name
        res["type"] = ResultType.Suite
        res["desc"] = self.description
        res["score"] = self.score
        res["valid"] = self.valid
        return res


class CommonSuite(Suite):
    def __init__(self, name: str, subsuites: list, namespace=None, options=None):
        super().__init__(name, subsuites, namespace, options)
        self.type = "Common"

    def build(self):
        # override
        # build workloads
        log.info('build common suite : %s', self.name)
        for workload in self.workloads:
            workload.build()
        # build subsuites
        for suite in self.sub_suites:
            suite.build()

    def run(self):
        # override
        # run workloads
        log.info('run common suite : %s', self.name)
        for workload in self.workloads:
            workload.run()

        # run subsuites
        for suite in self.sub_suites:
            suite.run()

    def validate(self):
        # override
        log.debug('validate common suite')
        for workload in self.workloads:
            workload.validate()
        # run subsuites
        for suite in self.sub_suites:
            suite.validate()

    def calc_score(self):
        # override
        log.debug("calc common suite")
        score_list = list()
        if self.workloads:
            for workload in self.workloads:
                if not workload.valid or workload.index <= 0:
                    self.valid = False
                if workload.index > 0:
                    score_list.append(workload.index)
        else:
            for suite in self.sub_suites:
                suite.calc_score()
            for suite in self.sub_suites:
                if not suite.valid:
                    self.valid = False
                if suite.score > 0:
                    score_list.append(suite.score)
        if score_list:
            # calc geomean
            self.score = reduce(lambda x, y: x * y, score_list) ** (1.0 / len(score_list))
            self.score = round(self.score, 2)
        log.info("{} verify: {}, score: {}".format(self.name, self.valid, self.score))


class OptionDict(dict):
    def __init__(self):
        super().__init__()
        self.__setitem__(Sections.GENERAL, {})
        self.__setitem__(Sections.COMPILER, {})

    def set_attributes(self):
        for option in self.__getitem__(Sections.GENERAL).keys():
            setattr(self, option, self.__getitem__(Sections.GENERAL)[option])
